package com.java.cacheavalanche.service;


import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import com.java.cacheavalanche.Date2LongTypeAdapter;
import com.java.cacheavalanche.FixNumberTypeAdapter;
import com.java.cacheavalanche.entity.Inventory;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scripting.support.ResourceScriptSource;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

@Service
@Log4j2
public class QueryService {

    private static final Gson gson;

    static {
        FixNumberTypeAdapter fixNumberTypeAdapter = new FixNumberTypeAdapter(new Gson());
        gson = (new GsonBuilder()).registerTypeAdapter(Date.class, new Date2LongTypeAdapter()).registerTypeAdapter((new TypeToken<Map<String, Object>>() {
        }).getType(), fixNumberTypeAdapter).registerTypeAdapter((new TypeToken<List<Object>>() {
        }).getType(), fixNumberTypeAdapter).create();
    }

    /**
     * 测试数据，模拟数据库中的数据
     */
    private static final List<String> dataList = Arrays.asList("测试数据1", "测试数据2", "测试数据3", "测试数据4");

    private static final String CACHE_KEY = "dataList";

    private static final String INVENTORY_CACHE_KEY = "inventory_cache";

    private static final String INVENTORY_CACHE_KEY_HAS_INVNETORY = "inventory_cache_key_has_invnetory";

    /**
     * 默认库存数
     */
    private AtomicInteger inventory = new AtomicInteger(100);

    private AtomicInteger version = new AtomicInteger(0);

    @Autowired
    private RedisTemplate redisTemplate;

    public Map<String, Object> getInventory() {
        Map<String, Object> inventoryMap = new HashMap<>();
        inventoryMap.put("inventory", inventory.intValue());
        /**
         * 获取缓存里的库存值
         */
       // Integer inventoryCache = (Integer) redisTemplate.opsForValue().get(INVENTORY_CACHE_KEY);


        String inventory = (String) redisTemplate.opsForHash().get(INVENTORY_CACHE_KEY, INVENTORY_CACHE_KEY);

        inventoryMap.put("inventory-cache", inventory);
        return inventoryMap;
    }

    public List<String> queryV1() {

        List<String> data = (List<String>) redisTemplate.opsForValue().get(CACHE_KEY);
        if (null == data) {
            log.info("返回数据库中的数据");
            //为了更好的重现问题,此处模拟慢SQL,线程等待3秒
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            data = dataList;
            //将数据写入Redis
            log.info("将数据写入缓存 key -> {}, value -> {}", CACHE_KEY, data);
            redisTemplate.opsForValue().set(CACHE_KEY, data, 1, TimeUnit.MINUTES);
        } else {
            log.info("返回缓存中的数据");
        }
        return data;
    }

    /**
     * 使用同步锁解决缓存雪崩问题
     *
     * @return
     */
    public List<String> queryV2() {
        List<String> data = (List<String>) redisTemplate.opsForValue().get(CACHE_KEY);
        if (null == data) {
            //加锁，让第一个人进来先写Redis
            synchronized (this) {
                data = (List<String>) redisTemplate.opsForValue().get(CACHE_KEY);
                if (data == null) {
                    log.info("返回数据库中的数据");
                    //为了更好的重现问题,此处模拟慢SQL,线程等待3秒
                    try {
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    data = dataList;
                    //将数据写入Redis
                    log.info("将数据写入缓存 key -> {}, value -> {}", CACHE_KEY, data);
                    redisTemplate.opsForValue().set(CACHE_KEY, data, 1, TimeUnit.MINUTES);
                } else {
                    log.info("返回缓存中的数据");
                }
            }
        } else {
            log.info("返回缓存中的数据");
        }
        return data;
    }

    public List<String> queryV3() {
        return null;
    }

    /**
     * 更新库存(模拟双写不一致问题)
     *
     * @param updateNum
     */
    public void updateInventory(Integer updateNum) {
        //变更库存
        int i = inventory.addAndGet(updateNum);
        int versionNum = version.addAndGet(1);


        double v = Math.random() * 3000 ;
        try {
            Thread.sleep(new Double(v).longValue());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //变更后的库存值存redis

        Map<String,String> map =new HashMap<>();
        Inventory inventory = new Inventory();
        inventory.setWarehouse("0012");
        inventory.setSkuCode("iphone18");
        inventory.setInventoryNum(i);
        inventory.setVersion(versionNum);
        String field = inventory.getWarehouse() + "-" + inventory.getSkuCode();
        map.put(field, gson.toJson(inventory));
        redisTemplate.opsForHash().putAll(INVENTORY_CACHE_KEY,map);

     //   redisTemplate.opsForValue().set(INVENTORY_CACHE_KEY, i, 2, TimeUnit.MINUTES);
    }



    /**
     * 更新库存(模拟双写不一致问题)
     * lua更新
     * @param updateNum
     */
    public void updateInventoryV2(Integer updateNum) {
        //变更库存
        int i = inventory.addAndGet(updateNum);
        int versionNum = version.addAndGet(1);
//        double v = Math.random() * 3000 ;
//        try {
//            Thread.sleep(new Double(v).longValue());
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }

        //变更后的库存值存redis

        Map<String,String> map =new HashMap<>();
        Inventory inventory = new Inventory();
        inventory.setInventoryNum(i);
        inventory.setWarehouse("0012");
        inventory.setSkuCode("iphone18");

        inventory.setAvailableNum(inventory.getInventoryNum() - inventory.getLockedNum());
        inventory.setVersion(versionNum);
        String field = inventory.getWarehouse() + "-" + inventory.getSkuCode();
        map.put(field, gson.toJson(inventory));


//        Inventory inventory2 = new Inventory();
//        inventory2.setInventoryNum(i);
//        inventory2.setWarehouse("0014");
//        inventory2.setSkuCode("iphone18 pro max");
//
//        inventory2.setVersion(versionNum);
//        inventory2.setAvailableNum(inventory.getInventoryNum() - inventory.getLockedNum());
//        String field2 = inventory2.getWarehouse() + "-" + inventory2.getSkuCode();
//     //   map.put(field2, gson.toJson(inventory2));
//
//        Inventory inventory3 = new Inventory();
//        BeanUtils.copyProperties(inventory2,inventory3);
//        inventory3.setWarehouse("0013");
//        inventory3.setAvailableNum(inventory3.getInventoryNum() - inventory3.getLockedNum());
//        //inventory3.setAvailableNum(0);
//        String field3 = inventory3.getWarehouse() + "-" + inventory3.getSkuCode();
//        map.put(field3, gson.toJson(inventory3));

        //redisTemplate.opsForHash().putAll(INVENTORY_CACHE_KEY,map);

        DefaultRedisScript<String> redisScript = new DefaultRedisScript<>();
        //返回类型是Long
        redisScript.setResultType(String.class);
        long start = System.currentTimeMillis();
        redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("redis/redis_hash.lua")));


        Object prize_stock = redisTemplate.execute(redisScript, Arrays.asList(INVENTORY_CACHE_KEY,INVENTORY_CACHE_KEY_HAS_INVNETORY),gson.toJson(map));
        //   redisTemplate.opsForValue().set(INVENTORY_CACHE_KEY, i, 2, TimeUnit.MINUTES);
        log.info("redis by lua 耗时:{}",System.currentTimeMillis() - start);

        System.out.println(prize_stock);
    }

}
